var WebSocketClient = require('websocket').client;

var AppUtils = require('../utils/AppUtils');
var IPAddress = require('./IPAddress');
var WSMsg = require('./WSMsg');

class WSClient {

  constructor(ip, targetPort, connState) {
    this.appUtils = AppUtils.getSingle();
    // 设置当前客户端连接状态（在于服务端建立连接和同步用）
    this.connState = connState ? connState : WSClient.ConnState.Off;
    // 客户端 ip
    this.targetIpAddress = new IPAddress(ip, targetPort);

    //保存当前wsClient 里与远程ws服务端通信的连接
    this.clientConnection = null;
    //WebSocket客户端
    this.webSocketClient = new WebSocketClient();

    //将当前WSClient对象设置为 websokectClient 的一个属性，方便事件中访问
    this.webSocketClient.thisWSClient = this;

    //用户自定义消息处理函数
    this.userClientOnMsgFunc = null;

    this.regClientEvent();
  }

  /**
   * 0.1 设置接收消息事件方法 - 提供给p2p模块使用者
   * @param func(msg, connObj, wsClientObj)
   */
  setOnMsgEvent(func) {
    if (func && func instanceof Function) {
      this.userClientOnMsgFunc = func;
    } else { 
      throw new Error('func 必须是一个函数');
    }
  }

  /**
   * 1.0 开始连接服务端
   * @param connState 本次连接类型（入网连接节点/连接普通节点）
   */
  connect() {
    if (!this.userClientOnMsgFunc || !(this.userClientOnMsgFunc instanceof Function)) { 
      throw new Error('缺少用户通信消息处理函数~！');
    }

    this.webSocketClient.connect(this.targetIpAddress.getURI(), 'echo-protocol');
    this.appUtils.logger.logCTitle('connect', '客户端[' + this.appUtils.wsIpAddress.getURI() + ']请求与服务端[' + this.targetIpAddress.getURI() + ']建立连接开始~！');
  }

  /**
   * 2.0 注册连接 事件
   */
  regClientEvent() {
    this.webSocketClient.on('connect', function (connection) {

      var appUtils = AppUtils.getSingle();
      connection.thisWSClient = this.thisWSClient;
      // 将连接通道存入 当前连接通道对象
      this.thisWSClient.clientConnection = connection;
    
      // 2.1 调用连接完成后的代码
      this.thisWSClient.onConnected(connection);
      // 2.2 消息事件
      connection.on('message', function (message) {
        if (message.type === 'utf8') {
          //调用消息处理方法
          WSClient.onMessage(message.utf8Data, connection);
        }
      });

      // 2.3 连接失败事件
      connection.on('connectFailed', function (error) {
        appUtils.logger.logCContent('onConFailed', '向服务端[' + this.thisWSClient.targetIpAddress.getURI() + ']连接 请求失败 Error: ' + error.toString());
      });

      // 2.4 连接关闭事件
      connection.on('close', function () {
        appUtils.logger.logCContent('onConClose', '服务端[' + this.thisWSClient.targetIpAddress.getURI() + ']主动关闭连接！');
        AppUtils.getSingle().clientConnectionList.remove(this.thisWSClient.targetIpAddress.getURI());
      });

      // 2.5 连接错误事件
      connection.on('error', function (error) {
        appUtils.logger.logCContent('onConError', '与服务端[' + this.thisWSClient.targetIpAddress.getURI() + ']连接 异常 Error: ' + error.toString());
      });
    });
  }

  /**
   * 2.0 连接成功事件方法
   * @param connObj 与服务端连接的通道
   */
  onConnected(connObj) {

    this.appUtils.logger.logCContent('onConnected', '与服务端[' + this.targetIpAddress.getURI() + ']建立MC2S连接成功~！'+ this.connState, this.appUtils.clientConnectionList);

    // 1.0 将当前客户端连接 添加到 客户端连接集合中
    this.appUtils.clientConnectionList.add(this.targetIpAddress.getURI(), connObj);

    // 2.0 默认动作：获取在网节点列表 (1,ws://127.0.0.1:1102)
    // 2.1 创建 ws 消息对象
    var wsMsg = new WSMsg();
    
    // 如果是刚启动的节点服务，需要向在网节点请求 在网节点列表
    // 但由于服务端无法知道 客户端的 ipaddress，所以需要通过消息发送给服务端

    // 2.1.1 如果当前客户端连接状态为 Off，未与任何节点连接，请求在网列表
    //       同时向服务端告知 当前客户端 wsServer 的 ipaddress
    if (this.connState == WSClient.ConnState.Off) {
      // a.设置消息内容（请求服务端在线节点列表，本客户端所在机器的服务端uri）
      wsMsg.SetStateAndContent(WSMsg.StateType.RequestOnlineNodeList, this.appUtils.wsIpAddress);
      // b.发送消息
      this.clientConnection.sendUTF(wsMsg.toJson());

      // c.更新客户端连接状态
      this.connState = WSClient.ConnState.GotOnlineList;
      this.appUtils.logger.logCContent('onConnected', '[客户端]发送 请求在网列表 消息：', wsMsg);
    }
    // 2.1.2 连接在网列表里的节点时使用
    else if (this.connState == WSClient.ConnState.GotOnlineList) {
      // 普通动作：连接在网节点，不需要请求在网列表
      wsMsg.SetStateAndContent(WSMsg.StateType.Connect, this.appUtils.wsIpAddress);
      // 发送消息
      this.clientConnection.sendUTF(wsMsg.toJson());
      this.appUtils.logger.logCContent('onConnected', '[客户端]向在网节点发送连接请求，内容：', wsMsg);
    }
    // 2.1.3 服务端建立双向连接后，将该连接存入服务端的连接集合中
    else if (this.connState == WSClient.ConnState.DoubleConnected) { 
      this.appUtils.logger.logCContent('onConnected', '服务端发起与第一次请求自己的客户端的双向连接~，成功~！');
    }

    this.appUtils.logger.logCContent('onConnected','连接成功后 ClientConn集合=', this.appUtils.clientConnectionList);
    
    this.appUtils.logger.logCContent('onConnected','连接成功后 在网节点=', this.appUtils.onlineNodeList);
  }

  /**
   * 2.2 消息监听事件
   * @param msgUtf8 收到的UTF8消息字符串
   */
  static onMessage(msgUtf8, connObj) {
    AppUtils.getSingle().logger.logCContent('onMessage', '服务端[' + connObj.remoteAddress + ']发来消息：', msgUtf8);

    if (WSMsg.isWSMsg(msgUtf8)) {
      var wsMsg = WSMsg.json2WSMsgObj(msgUtf8);

      if (wsMsg.state === WSMsg.StateType.ResponseOnlineNodeList) {
        var newOnlineNodeArr = JSON.parse(wsMsg.content);
        AppUtils.getSingle().onlineNodeList.compareAdd(newOnlineNodeArr);
        // 标记为已连接初始节点
        connObj.thisWSClient.connState = WSClient.ConnState.GotOnlineList;
        // 连接 在网节点
        this.connectAllNodes(connObj);
      }
      
    } else {
      //调用用户自定义的 消息处理方法
      connObj.thisWSClient.userClientOnMsgFunc(msgUtf8, connObj, connObj.thisWSClient);
    }
    // if (wsMsg.state === WSMsg.StateType.BroadNewBlockData) { }
    // if (wsMsg.state === WSMsg.StateType.SynchronousBlockData) { }
  }

  /**
   * 3. 连接所有在网节点
   */
  static connectAllNodes(connObj) {
    var appUtils = AppUtils.getSingle();

    var myServerURI = appUtils.wsIpAddress.getURI();
    appUtils.logger.logCTitle('connectAllNodes', '开始，在网节点：',appUtils.onlineNodeList);
    // 循环列表，拿每个节点 和 连接列表 中进行比较，如果有不存在的，则建立新连接，并存入 连接列表
    for (var i = 0; i < appUtils.onlineNodeList.length(); i++) {
      var nodeIpAddr = appUtils.onlineNodeList.getByIndex(i);

      // 如果不存在该连接(不等于本机服务端，和 已存在的连接)
      if (nodeIpAddr.getURI() != myServerURI && !appUtils.clientConnectionList.includes(nodeIpAddr.getURI())) {

        var client = new WSClient(nodeIpAddr.ip, nodeIpAddr.port, WSClient.ConnState.GotOnlineList);
        client.setOnMsgEvent(connObj.thisWSClient.userClientOnMsgFunc);
        client.connect();

        //appUtils.logger.logCContent('connectAllNodes', '客户端【' + myServerURI + '】正在请求连接 节点：' + nodeIpAddr.getURI());
      }
    }    
    appUtils.logger.logCTitle('connectAllNodes', '结束');
  }
  
}


WSClient.ConnState = {
  Off: 0, // 未与任何节点连接
  GotOnlineList: 1, // 入网连接节点，并已获取 在网列表
  DoubleConnected: 2 // 已建立双向连接
};

module.exports = WSClient;